package org.raymond.iworks.gateway.authenticate;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.raymond.iworks.apis.common.ErrorCode;
import org.raymond.iworks.apis.exception.BaseException;
import org.raymond.iworks.commons.constants.CacheConstants;
import org.raymond.iworks.commons.constants.CommonConstants;
import org.raymond.iworks.core.constants.LockConstants;
import org.raymond.iworks.core.holder.CacheHolder;
import org.raymond.iworks.core.holder.LockHolder;
import org.raymond.iworks.core.holder.SpringContextHolder;
import org.raymond.iworks.core.security.UsernameCertification;
import org.raymond.iworks.core.utils.RequestUtil;
import org.raymond.iworks.gateway.filter.support.FilterOrder;
import org.raymond.iworks.gateway.filter.support.GatewayContext;
import org.raymond.iworks.gateway.utils.ReactiveWebUtil;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Slf4j
public class LoginFilter implements GlobalFilter, Ordered {
    private static final String[] LoginPaths = new String[]{"/admin/user/login", "/user/login"};

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private LockHolder lockHolder;
    @Autowired
    private CacheHolder cacheHolder;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = RequestUtil.getRelativePath(request);
        if(!isLogin(path)){
            return chain.filter(exchange);
        }

        GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
        if(gatewayContext==null){
            throw new BaseException(ErrorCode.GATEWAY_ERROR, "Gateway context is null");
        }

        MultiValueMap<String, String> loginMap = gatewayContext.getAllRequestData();
        String loginType = String.valueOf(loginMap.getFirst(CommonConstants.LOGIN_TYPE_KEY));
        String username = String.valueOf(loginMap.getFirst(CommonConstants.LOGIN_USERNAME_KEY));
        String password = String.valueOf(loginMap.getFirst(CommonConstants.LOGIN_PASSWORD_KEY));
        if(StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            throw new BaseException(ErrorCode.UNAUTHORIZED, "username or password is blank");
        }
        RBloomFilter<String> fullFilter = lockHolder.getBloomFilter(LockConstants.BLOOM_FILTER_USERNAME);
        RBloomFilter<String> deletedFilter = lockHolder.getBloomFilter(LockConstants.BLOOM_FILTER_DEL_USERNAME);
        // todo 增加一个定时任务,定时重建布隆过滤器,然后用rename替换掉原来的
        if(!fullFilter.contains(username) || deletedFilter.contains(username)){
            String remoteAddress = ReactiveWebUtil.getRemoteAddress(exchange);
            cacheHolder.sSet(CacheConstants.BLACK_LIST_IP, remoteAddress);
            throw new BaseException(ErrorCode.NOT_FOUND, "username is not exist");
        }
        UsernameCertification certification = new UsernameCertification(username, password);
        Authentication authentication = SpringContextHolder.getBean(UsernameAuthentication.class);
        String token = authentication.authenticate(certification);
        ServerHttpRequest newRequest = addTokenHeader(exchange, token);
        return chain.filter(exchange.mutate().request(newRequest).build());
    }

    @Override
    public int getOrder() {
        return FilterOrder.LoginFilter;
    }

    private boolean isLogin(String requestPath){
        return StringUtils.equalsAnyIgnoreCase(requestPath, LoginPaths);
    }

    public ServerHttpRequest addTokenHeader(ServerWebExchange exchange, String token){
        ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> httpHeaders.set(CacheConstants.KEY_TOKEN, CacheConstants.KEY_TOKEN_PREFIX + token)).build();
        return request;
    }
}
